package com.derbysoft.lowcostadapter.commons.repository.impl;

import com.derbysoft.common.paginater.Paginater;
import com.derbysoft.lowcostadapter.commons.model.Hotel;
import com.derbysoft.lowcostadapter.commons.model.Status;
import com.derbysoft.lowcostadapter.commons.repository.HotelRepository;
import org.hibernate.criterion.DetachedCriteria;
import org.hibernate.criterion.MatchMode;
import org.hibernate.criterion.Order;
import org.hibernate.criterion.Restrictions;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Repository("hotelRepository")
public class HotelRepositoryImpl extends CommonService<Hotel> implements HotelRepository {

    public static final String PROPERTY_ID = "propertyID";
    public static final String STATUS = "status";
    public static final String PASSPORT = "passport";

    @Override
    public Hotel findByPassport(final String passport, final Status status) {
        return load(new String[]{PASSPORT, STATUS}, new Object[]{passport, status});
    }

    @Override
    public Hotel findByPassport(String passport) {
        return load(PASSPORT, passport);
    }

    @Override
    public Hotel load(int propertyID) {
        return load(PROPERTY_ID, propertyID);
    }

    @Override
    public Hotel findByPropertyID(final int propertyID, final Status status) {
        return load(new String[]{PROPERTY_ID, STATUS}, new Object[]{propertyID, status});
    }

    @Override
    public void create(Integer propertyID, String passport, String name) {
        if (load(propertyID) != null) {
            throw new IllegalArgumentException(String.format("PropertyID [%d] already in use.", propertyID));
        }

        if (findByPassport(passport, null) != null) {
            throw new IllegalArgumentException(String.format("Passport [%s] already in use.", passport));
        }

        Hotel hotel = new Hotel();
        hotel.setPropertyID(propertyID);
        hotel.setPassport(passport);
        hotel.setName(name);
        hotel.setStatus(Status.ENABLED);
        super.save(hotel);
    }

    @Override
    public Paginater findByCondition(final Hotel condition, Paginater paginater) {
        DetachedCriteria criteria = DetachedCriteria.forClass(Hotel.class).addOrder(Order.desc(PROPERTY_ID));
        if (condition != null) {
            appendToCriteria(condition, criteria);
        }
        return super.paginater(criteria, paginater);
    }

    private void appendToCriteria(Hotel condition, DetachedCriteria criteria) {
        if (condition.getPassport() != null) {
            criteria.add(Restrictions.ilike(PASSPORT, condition.getPassport(), MatchMode.ANYWHERE));
        }

        if (condition.getName() != null) {
            criteria.add(Restrictions.ilike("name", condition.getName(), MatchMode.ANYWHERE));
        }

        if (condition.getPropertyID() != 0) {
            criteria.add(Restrictions.eq(PROPERTY_ID, condition.getPropertyID()));
        }

        if (condition.getStatus() != null) {
            criteria.add(Restrictions.eq(STATUS, condition.getStatus()));
        }
    }


    @Override
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void update(Integer propertyID, String passport, String name, Status status) {
        Hotel hotel = load(propertyID);
        if (hotel == null) {
            throw new IllegalArgumentException(String.format("PropertyID [%d] not found.", propertyID));
        }

        Hotel hotelByPassport = findByPassport(passport, null);
        if (hotelByPassport != null) {
            throw new IllegalArgumentException(String.format("Passport [%s] already in use.", passport));
        }

        hotel.setPassport(passport);
        hotel.setName(name);
        hotel.setStatus(status);

        super.save(hotel);
    }

    @Override
    public List<Hotel> list(final Status status) {
        DetachedCriteria criteria = DetachedCriteria.forClass(Hotel.class);
        criteria.add(Restrictions.eq(STATUS, status));
        return find(criteria);
    }

    @Override
    public void saveAll(List<Hotel> hotels) {
        for (Hotel hotel : hotels) {
            super.save(hotel);
        }
    }

    @Override
    public Map<Integer, Hotel> loadHotelMap(Status status) {
        List<Hotel> hotels = list(status);

        Map<Integer, Hotel> hotelMap = new HashMap<Integer, Hotel>(hotels.size());
        for (Hotel hotel : hotels) {
            hotelMap.put(hotel.getPropertyID(), hotel);
        }

        return hotelMap;
    }
}
